/* Copyright (c) Microsoft Corporation.
   Licensed under the MIT License. */

/***************************************************************************
	Author: ShonK
	Project: Kauai
	Reviewed:
	Copyright (c) Microsoft Corporation

	GFX classes: graphics device (GDV), graphics environment (GNV)

***************************************************************************/
#include "frame.h"
ASSERTNAME


APT vaptGray = { 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55 };
APT vaptLtGray = { 0x22, 0x88, 0x44, 0x11, 0x22, 0x88, 0x44, 0x11 };
APT vaptDkGray = { 0xDD, 0x77, 0xBB, 0xEE, 0xDD, 0x77, 0xBB, 0xEE };

NTL vntl;


RTCLASS(GNV)
RTCLASS(GPT)
RTCLASS(NTL)
RTCLASS(OGN)


const long kdtsMaxTrans = 30 * kdtsSecond;
long vcactRealize;


/***************************************************************************
	Set the ACR from the lw.  The lw should have been returned by a call
	to ACR::LwGet().
***************************************************************************/
void ACR::SetFromLw(long lw)
{
	_lu = (ulong)lw;
	AssertThis(0);
}


/***************************************************************************
	Get an lw from the ACR.  The lw can then be stored on file, reread
	and passed to ACR::SetFromLw.  Valid non-nil colors always return
	non-zero, so zero can be used as a nil value.
***************************************************************************/
long ACR::LwGet(void) const
{
	return (long)_lu;
}


/***************************************************************************
	Get a clr from the ACR.  Asserts that the acr is an rgb color.
***************************************************************************/
void ACR::GetClr(CLR *pclr)
{
	AssertThis(facrRgb);
	AssertVarMem(pclr);

	pclr->bRed = B2Lw(_lu);
	pclr->bGreen = B1Lw(_lu);
	pclr->bBlue = B0Lw(_lu);
	pclr->bZero = 0;
}


#ifdef DEBUG
/***************************************************************************
	Assert that the acr is a valid color.
***************************************************************************/
void ACR::AssertValid(ulong grfacr)
{
	switch (B3Lw(_lu))
		{
	case kbNilAcr:
		Assert(grfacr == facrNil, "unexpected nil ACR");
		break;
	case kbRgbAcr:
		Assert(grfacr == facrNil || (grfacr & facrRgb), "unexpected RGB ACR");
		break;
	case kbIndexAcr:
		Assert(B2Lw(_lu) == 0 && B1Lw(_lu) == 0, "bad Index ACR");
		Assert(grfacr == facrNil || (grfacr & facrIndex), "unexpected Index ACR");
		break;
	case kbSpecialAcr:
		Assert(_lu == kluAcrClear || _lu == kluAcrInvert, "unknown acr");
		Assert(grfacr == facrNil, "unexpected Special ACR");
		break;
	default:
		BugVar("invalid ACR", &_lu);
		break;
		}
}
#endif //DEBUG


/***************************************************************************
	Change the origin on the pattern.
***************************************************************************/
void APT::MoveOrigin(long dxp, long dyp)
{
	//this cast to ulong works because 2^32 is a multiple of 8.
	dxp = (ulong)dxp % 8;
	dyp = (ulong)dyp % 8;
	if (dxp != 0)
		{
		byte *pb;

		for (pb = rgb + 8; pb-- != rgb; )
			*pb = (*pb >> dxp) | (*pb << (8 - dxp));
		}
	if (dyp != 0)
		SwapBlocks(rgb, 8 - dyp, dyp);
}


/***************************************************************************
	Constructor for Graphics environment.
***************************************************************************/
GNV::GNV(GPT *pgpt)
{
	AssertPo(pgpt, 0);

	_Init(pgpt);
	AssertThis(0);
}


/***************************************************************************
	Constructor for Graphics environment based on a pgob.
***************************************************************************/
GNV::GNV(PGOB pgob)
{
	AssertPo(pgob, 0);

	_Init(pgob->Pgpt());	//use the GOB's port
	SetGobRc(pgob);			//set the rc's according to the gob
	AssertThis(0);
}


/***************************************************************************
	Constructor for Graphics environment based on both a port and a pgob.
***************************************************************************/
GNV::GNV(PGOB pgob, PGPT pgpt)
{
	AssertPo(pgpt, 0);
	AssertPo(pgob, 0);

	_Init(pgpt);
	SetGobRc(pgob);
	AssertThis(0);
}


/***************************************************************************
	Destructor for the GNV.
***************************************************************************/
GNV::~GNV(void)
{
	AssertThis(0);
	Mac( _pgpt->Unlock(); )
	ReleasePpo(&_pgpt);
}


/***************************************************************************
	Fill in all fields of the gnv with default values.
***************************************************************************/
void GNV::_Init(PGPT pgpt)
{
	PT pt;

	_pgpt = pgpt;
	pgpt->AddRef();
	Mac( pgpt->Lock(); )
	_rcSrc.Set(0, 0, 1, 1);
	pgpt->GetPtBase(&pt);
	_rcDst.OffsetCopy(&_rcSrc, -pt.xp, -pt.yp);
	_xp = _yp = 0;

	_dsf.onn = vntl.OnnSystem();
	_dsf.grfont = fontNil;
	_dsf.dyp = vpappb->DypTextDef();
	_dsf.tah = tahLeft;
	_dsf.tav = tavTop;

	_gdd.dxpPen = _gdd.dypPen = 1;
	_gdd.prcsClip = pvNil;
	_rcVis.Max();
}


/***************************************************************************
	Set the mapping and vis according to the gob.
***************************************************************************/
void GNV::SetGobRc(PGOB pgob)
{
	RC rc;

	//set the mapping
	pgob->GetRc(&rc, cooGpt);
	SetRcDst(&rc);
	pgob->GetRc(&rc, cooLocal);
	SetRcSrc(&rc);
	pgob->GetRcVis(&rc, cooLocal);
	SetRcVis(&rc);
}


#ifdef DEBUG
/***************************************************************************
	Assert the validity of the gnv
***************************************************************************/
void GNV::AssertValid(ulong grf)
{
	GNV_PAR::AssertValid(0);
	AssertPo(_pgpt, 0);
	AssertPo(&_dsf, 0);
	Assert(!_rcSrc.FEmpty(), "empty src rectangle");
	Assert(!_rcDst.FEmpty(), "empty dst rectangle");
	Assert(_gdd.prcsClip == pvNil || _gdd.prcsClip == &_rcsClip,
		"bad _gdd.prcsClip");
}


/***************************************************************************
	Mark memory for the GNV.
***************************************************************************/
void GNV::MarkMem(void)
{
	AssertValid(0);
	GNV_PAR::MarkMem();
	MarkMemObj(_pgpt);
}
#endif //DEBUG


/***************************************************************************
	Fill a rectangle with a two color pattern.
***************************************************************************/
void GNV::FillRcApt(RC *prc, APT *papt, ACR acrFore, ACR acrBack)
{
	AssertThis(0);
	AssertVarMem(prc);
	AssertVarMem(papt);
	AssertPo(&acrFore, 0);
	AssertPo(&acrBack, 0);

	RCS rcs;

	if (!_FMapRcRcs(prc, &rcs))
		return;

	_gdd.grfgdd = fgddFill | fgddPattern;
	_gdd.apt = *papt;
	_gdd.apt.MoveOrigin(_rcDst.xpLeft - _rcSrc.xpLeft, _rcDst.ypTop - _rcSrc.ypTop);
	_gdd.acrFore = acrFore;
	_gdd.acrBack = acrBack;

	_pgpt->DrawRcs(&rcs, &_gdd);
}


/***************************************************************************
	Fill a rectangle with a color.
***************************************************************************/
void GNV::FillRc(RC *prc, ACR acr)
{
	AssertThis(0);
	AssertVarMem(prc);
	AssertPo(&acr, 0);

	RCS rcs;

	if (!_FMapRcRcs(prc, &rcs))
		return;

	_gdd.grfgdd = fgddFill;
	_gdd.acrFore = acr;

	_pgpt->DrawRcs(&rcs, &_gdd);
}


/***************************************************************************
	Frame a rectangle with a two color pattern.
***************************************************************************/
void GNV::FrameRcApt(RC *prc, APT *papt, ACR acrFore, ACR acrBack)
{
	AssertThis(0);
	AssertVarMem(prc);
	AssertVarMem(papt);
	AssertPo(&acrFore, 0);
	AssertPo(&acrBack, 0);

	RCS rcs;

	if (!_FMapRcRcs(prc, &rcs) || _gdd.dxpPen == 0 && _gdd.dypPen == 0)
		return;

	_gdd.grfgdd = fgddFrame | fgddPattern;
	_gdd.apt = *papt;
	_gdd.apt.MoveOrigin(_rcDst.xpLeft - _rcSrc.xpLeft, _rcDst.ypTop - _rcSrc.ypTop);
	_gdd.acrFore = acrFore;
	_gdd.acrBack = acrBack;

	_pgpt->DrawRcs(&rcs, &_gdd);
}


/***************************************************************************
	Frame a rectangle with a color.
***************************************************************************/
void GNV::FrameRc(RC *prc, ACR acr)
{
	AssertThis(0);
	AssertVarMem(prc);
	AssertPo(&acr, 0);

	RCS rcs;

	if (!_FMapRcRcs(prc, &rcs) || _gdd.dxpPen == 0 && _gdd.dypPen == 0)
		return;

	_gdd.grfgdd = fgddFrame;
	_gdd.acrFore = acr;

	_pgpt->DrawRcs(&rcs, &_gdd);
}


/***************************************************************************
	For hilighting text.  On mac, interchanges the system hilite color and
	the background color.  On Win, just inverts.
***************************************************************************/
void GNV::HiliteRc(RC *prc, ACR acrBack)
{
	AssertThis(0);
	AssertVarMem(prc);

	RCS rcs;

	if (!_FMapRcRcs(prc, &rcs))
		return;
	_gdd.acrBack = acrBack;
	_pgpt->HiliteRcs(&rcs, &_gdd);
}


/***************************************************************************
	Fill an oval with a two color pattern.
***************************************************************************/
void GNV::FillOvalApt(RC *prc, APT *papt, ACR acrFore, ACR acrBack)
{
	AssertThis(0);
	AssertVarMem(prc);
	AssertVarMem(papt);
	AssertPo(&acrFore, 0);
	AssertPo(&acrBack, 0);

	RCS rcs;

	if (!_FMapRcRcs(prc, &rcs))
		return;

	_gdd.grfgdd = fgddFill | fgddPattern;
	_gdd.apt = *papt;
	_gdd.apt.MoveOrigin(_rcDst.xpLeft - _rcSrc.xpLeft, _rcDst.ypTop - _rcSrc.ypTop);
	_gdd.acrFore = acrFore;
	_gdd.acrBack = acrBack;

	_pgpt->DrawOval(&rcs, &_gdd);
}


/***************************************************************************
	Fill an oval with a color.
***************************************************************************/
void GNV::FillOval(RC *prc, ACR acr)
{
	AssertThis(0);
	AssertVarMem(prc);
	AssertPo(&acr, 0);

	RCS rcs;

	if (!_FMapRcRcs(prc, &rcs))
		return;

	_gdd.grfgdd = fgddFill;
	_gdd.acrFore = acr;

	_pgpt->DrawOval(&rcs, &_gdd);
}


/***************************************************************************
	Frame an oval with a two color pattern.
***************************************************************************/
void GNV::FrameOvalApt(RC *prc, APT *papt, ACR acrFore, ACR acrBack)
{
	AssertThis(0);
	AssertVarMem(prc);
	AssertVarMem(papt);
	AssertPo(&acrFore, 0);
	AssertPo(&acrBack, 0);

	RCS rcs;

	if (!_FMapRcRcs(prc, &rcs) || _gdd.dxpPen == 0 && _gdd.dypPen == 0)
		return;

	_gdd.grfgdd = fgddFrame | fgddPattern;
	_gdd.apt = *papt;
	_gdd.apt.MoveOrigin(_rcDst.xpLeft - _rcSrc.xpLeft, _rcDst.ypTop - _rcSrc.ypTop);
	_gdd.acrFore = acrFore;
	_gdd.acrBack = acrBack;

	_pgpt->DrawOval(&rcs, &_gdd);
}


/***************************************************************************
	Frame an oval with a color.
***************************************************************************/
void GNV::FrameOval(RC *prc, ACR acr)
{
	AssertThis(0);
	AssertVarMem(prc);
	AssertPo(&acr, 0);

	RCS rcs;

	if (!_FMapRcRcs(prc, &rcs) || _gdd.dxpPen == 0 && _gdd.dypPen == 0)
		return;

	_gdd.grfgdd = fgddFrame;
	_gdd.acrFore = acr;

	_pgpt->DrawOval(&rcs, &_gdd);
}


/***************************************************************************
	Draw a line with a pattern.  Sets the pen position to (xp2, yp2).
***************************************************************************/
void GNV::LineApt(long xp1, long yp1, long xp2, long yp2,
	APT *papt, ACR acrFore, ACR acrBack)
{
	AssertThis(0);
	AssertVarMem(papt);
	AssertPo(&acrFore, 0);
	AssertPo(&acrBack, 0);
	PTS pts1, pts2;

	if (_gdd.dxpPen != 0 || _gdd.dypPen != 0)
		{
		_gdd.grfgdd = fgddFrame | fgddPattern;
		_gdd.apt = *papt;
		_gdd.apt.MoveOrigin(_rcDst.xpLeft - _rcSrc.xpLeft, _rcDst.ypTop - _rcSrc.ypTop);
		_gdd.acrFore = acrFore;
		_gdd.acrBack = acrBack;
	
		_MapPtPts(xp1, yp1, &pts1);
		_MapPtPts(xp2, yp2, &pts2);
		_pgpt->DrawLine(&pts1, &pts2, &_gdd);
		}
	_xp = xp2;
	_yp = yp2;
}


/***************************************************************************
	Draw a line in a solid color.  Sets the pen position to (xp2, yp2).
***************************************************************************/
void GNV::Line(long xp1, long yp1, long xp2, long yp2, ACR acr)
{
	AssertThis(0);
	AssertPo(&acr, 0);
	PTS pts1, pts2;

	if (_gdd.dxpPen != 0 || _gdd.dypPen != 0)
		{
		_gdd.grfgdd = fgddFrame;
		_gdd.acrFore = acr;
	
		_MapPtPts(xp1, yp1, &pts1);
		_MapPtPts(xp2, yp2, &pts2);
		_pgpt->DrawLine(&pts1, &pts2, &_gdd);
		}
	_xp = xp2;
	_yp = yp2;
}


/***************************************************************************
	Fill a polygon with a pattern.
***************************************************************************/
void GNV::FillOgnApt(POGN pogn, APT *papt, ACR acrFore, ACR acrBack)
{
	AssertThis(0);
	AssertPo(pogn, 0);
	AssertVarMem(papt);
	AssertPo(&acrFore, 0);
	AssertPo(&acrBack, 0);
	HQ hqoly;

	if ((hqoly = _HqolyCreate(pogn, fognAutoClose)) == hqNil)
		{
		PushErc(ercGfxCantDraw);
		return;
		}

	_gdd.grfgdd = fgddFill | fgddPattern;
	_gdd.apt = *papt;
	_gdd.apt.MoveOrigin(_rcDst.xpLeft - _rcSrc.xpLeft, _rcDst.ypTop - _rcSrc.ypTop);
	_gdd.acrFore = acrFore;
	_gdd.acrBack = acrBack;

	_pgpt->DrawPoly(hqoly, &_gdd);
	FreePhq(&hqoly);
}


/***************************************************************************
	Fill a polygon with a color.
***************************************************************************/
void GNV::FillOgn(POGN pogn, ACR acr)
{
	AssertThis(0);
	AssertPo(pogn, 0);
	AssertPo(&acr, 0);
	HQ hqoly;

	if ((hqoly = _HqolyCreate(pogn, fognAutoClose)) == hqNil)
		{
		PushErc(ercGfxCantDraw);
		return;
		}

	_gdd.grfgdd = fgddFill;
	_gdd.acrFore = acr;

	_pgpt->DrawPoly(hqoly, &_gdd);
	FreePhq(&hqoly);
}


/***************************************************************************
	Frame a polygon with a pattern.
	NOTE: Using kacrInvert produces slightly different results on the Mac.
	(Mac only does alternate winding).
***************************************************************************/
void GNV::FrameOgnApt(POGN pogn, APT *papt, ACR acrFore, ACR acrBack)
{
	AssertThis(0);
	AssertPo(pogn, 0);
	AssertVarMem(papt);
	AssertPo(&acrFore, 0);
	AssertPo(&acrBack, 0);
	HQ hqoly;

	if (_gdd.dxpPen == 0 && _gdd.dypPen == 0)
		return;

	if (hqNil == (hqoly = _HqolyFrame(pogn, fognAutoClose)))
		{
		PushErc(ercGfxCantDraw);
		return;
		}

	_gdd.grfgdd = fgddFrame | fgddPattern;
	_gdd.apt = *papt;
	_gdd.apt.MoveOrigin(_rcDst.xpLeft - _rcSrc.xpLeft, _rcDst.ypTop - _rcSrc.ypTop);
	_gdd.acrFore = acrFore;
	_gdd.acrBack = acrBack;

	_pgpt->DrawPoly(hqoly, &_gdd);
	FreePhq(&hqoly);
}


/***************************************************************************
	Frame a polygon with a color.
	NOTE: Using kacrInvert produces slightly different results on the Mac.
***************************************************************************/
void GNV::FrameOgn(POGN pogn, ACR acr)
{
	AssertThis(0);
	AssertPo(pogn, 0);
	AssertPo(&acr, 0);
	HQ hqoly;

	if (_gdd.dxpPen == 0 && _gdd.dypPen == 0)
		return;

	if (hqNil == (hqoly = _HqolyFrame(pogn, fognAutoClose)))
		{
		PushErc(ercGfxCantDraw);
		return;
		}

	_gdd.grfgdd = fgddFrame;
	_gdd.acrFore = acr;

	_pgpt->DrawPoly(hqoly, &_gdd);
	FreePhq(&hqoly);
}


/***************************************************************************
	Frame a poly-line with a pattern.
	NOTE: Using kacrInvert produces slightly different results on the Mac.
***************************************************************************/
void GNV::FramePolyLineApt(POGN pogn, APT *papt, ACR acrFore, ACR acrBack)
{
	AssertThis(0);
	AssertPo(pogn, 0);
	AssertVarMem(papt);
	AssertPo(&acrFore, 0);
	AssertPo(&acrBack, 0);
	HQ hqoly;

	if (_gdd.dxpPen == 0 && _gdd.dypPen == 0)
		return;

	if (hqNil == (hqoly = _HqolyFrame(pogn, fognNil)))
		{
		PushErc(ercGfxCantDraw);
		return;
		}

	_gdd.grfgdd = fgddFrame | fgddPattern;
	_gdd.apt = *papt;
	_gdd.apt.MoveOrigin(_rcDst.xpLeft - _rcSrc.xpLeft, _rcDst.ypTop - _rcSrc.ypTop);
	_gdd.acrFore = acrFore;
	_gdd.acrBack = acrBack;

	_pgpt->DrawPoly(hqoly, &_gdd);
	FreePhq(&hqoly);
}


/***************************************************************************
	Frame a poly-line with a color.
	NOTE: Using kacrInvert produces slightly different results on the Mac.
***************************************************************************/
void GNV::FramePolyLine(POGN pogn, ACR acr)
{
	AssertThis(0);
	AssertPo(pogn, 0);
	AssertPo(&acr, 0);
	HQ hqoly;

	if (_gdd.dxpPen == 0 && _gdd.dypPen == 0)
		return;

	if (hqNil == (hqoly = _HqolyCreate(pogn, fognNil)))
		{
		PushErc(ercGfxCantDraw);
		return;
		}

	_gdd.grfgdd = fgddFrame;
	_gdd.acrFore = acr;

	_pgpt->DrawPoly(hqoly, &_gdd);
	FreePhq(&hqoly);
}


/***************************************************************************
	Convert an OGN into a polygon record (hqoly).  This maps the points and
	optionally closes the polygon and/or calculates the bounds (Mac only).
***************************************************************************/
HQ GNV::_HqolyCreate(POGN pogn, ulong grfogn)
{
	AssertThis(0);
	AssertPo(pogn, 0);
	HQ hqoly;
	long ipt;
	long cb;
	long cpt;
	OLY *poly;
	PT *ppt;
	PTS *ppts;

	if ((cpt = pogn->IvMac()) < 2)
		return hqNil;

	cb = kcbOlyBase + LwMul(cpt, size(PTS));
	if (cpt < 3)
		grfogn &= ~fognAutoClose;
	else if (grfogn & fognAutoClose)
		cb += size(PTS);

	if (!FAllocHq(&hqoly, cb, fmemNil, mprNormal))
		return hqNil;

	poly = (OLY *)PvLockHq(hqoly);
#ifdef MAC
	poly->cb = (short)cb;
	Assert(cb == poly->cb, "polygon too big");
	poly->rcs.left = kswMax;
	poly->rcs.right = kswMin;
	poly->rcs.top = kswMax;
	poly->rcs.bottom = kswMin;
#else //!MAC
	poly->cpts = cpt + FPure(grfogn & fognAutoClose);
#endif //!MAC

	ppt = pogn->PrgptLock();
	ppts = (PTS *)poly->rgpts;
	for (ipt = 0; ipt < cpt; ipt++, ppt++, ppts++)
		{
		_MapPtPts(ppt->xp, ppt->yp, ppts);
#ifdef MAC
		// Compute bounding rectangle.
		if (poly->rcs.top > ppts->v)
			poly->rcs.top = ppts->v;
		if (poly->rcs.left > ppts->h)
			poly->rcs.left = ppts->h;
		if (poly->rcs.bottom < ppts->v)
			poly->rcs.bottom = ppts->v;
		if (poly->rcs.right < ppts->h)
			poly->rcs.right = ppts->h;
#endif //MAC
		}
	if (grfogn & fognAutoClose)
		*ppts = poly->rgpts[0];

	pogn->Unlock();
	UnlockHq(hqoly);
	return hqoly;
}


/***************************************************************************
	Convert a polygon (OGN) into a polygon record (hqoly).  On Windows,
	this actually generates a new polygon that is the outline of the framed
	path (which we'll tell GDI to fill).  On the Mac, this just calls
	_HqolyCreate.
***************************************************************************/
HQ GNV::_HqolyFrame(POGN pogn, ulong grfogn)
{
#ifdef WIN
	AssertThis(0);
	AssertPo(pogn, 0);
	HQ hqoly;

	POGN pognUse;
	PT rgptPen[4]; // Pen rectangle vectors.

	rgptPen[0].xp = 0;
	rgptPen[0].yp = 0;
	rgptPen[1].xp = _gdd.dxpPen;
	rgptPen[1].yp = 0;
	rgptPen[2].xp = _gdd.dxpPen;
	rgptPen[2].yp = _gdd.dypPen;
	rgptPen[3].xp = 0;
	rgptPen[3].yp = _gdd.dypPen;

	if (pvNil == (pognUse = pogn->PognTraceRgpt(rgptPen, 4, grfogn)))
		return pvNil;
	hqoly = _HqolyCreate(pognUse, grfogn);
	ReleasePpo(&pognUse);

	return hqoly;
#elif defined(MAC)
	return _HqolyCreate(pogn, grfogn);
#endif //MAC
}


/***************************************************************************
	Scroll the given rectangle by (dxp, dyp).  If prc1 is not nil fill it
	with the first uncovered rectangle.  If prc2 is not nil fill it
	with the second uncovered rectangle (if there is one).
***************************************************************************/
void GNV::ScrollRc(RC *prc, long dxp, long dyp, RC *prc1, RC *prc2)
{
	AssertThis(0);
	AssertVarMem(prc);
	AssertNilOrVarMem(prc1);
	AssertNilOrVarMem(prc2);
	PTS pts;
	RCS rcs;
	PT pt;

	if (!_FMapRcRcs(prc, &rcs) || dxp == 0 && dyp == 0)
		{
		if (pvNil != prc1)
			prc1->Zero();
		if (pvNil != prc2)
			prc2->Zero();
		return;
		}

	_MapPtPts(prc->xpLeft + dxp, prc->ypTop + dyp, &pts);
	pt = pts;
	_pgpt->ScrollRcs(&rcs, pt.xp - rcs.left, pt.yp - rcs.top, &_gdd);

	GetBadRcForScroll(prc, dxp, dyp, prc1, prc2);
}


/***************************************************************************
	Static method to get the RC's that are uncovered during a scroll
	operation.
***************************************************************************/
void GNV::GetBadRcForScroll(RC *prc, long dxp, long dyp, RC *prc1, RC *prc2)
{
	AssertNilOrVarMem(prc1);
	AssertNilOrVarMem(prc2);

	if (pvNil != prc1)
		{
		*prc1 = *prc;
		if (0 < dxp)
			prc1->xpRight = prc1->xpLeft + dxp;
		else if (0 > dxp)
			prc1->xpLeft = prc1->xpRight + dxp;
		else if (0 < dyp)
			prc1->ypBottom = prc1->ypTop + dyp;
		else
			prc1->ypTop = prc1->ypBottom + dyp;
		prc1->FIntersect(prc);
		}

	if (pvNil != prc2)
		{
		if (0 == dxp || 0 == dyp)
			prc2->Zero();
		else
			{
			*prc2 = *prc;
			if (0 < dyp)
				prc2->ypBottom = prc2->ypTop + dyp;
			else
				prc2->ypTop = prc2->ypBottom + dyp;
			if (0 < dxp)
				prc2->xpLeft += dxp;
			else
				prc2->xpRight += dxp;
			prc2->FIntersect(prc);
			}
		}
}


/***************************************************************************
	Get the source rectangle.
***************************************************************************/
void GNV::GetRcSrc(RC *prc)
{
	AssertThis(0);
	AssertVarMem(prc);
	*prc = _rcSrc;
}


/***************************************************************************
	Set the source rectangle.
***************************************************************************/
void GNV::SetRcSrc(RC *prc)
{
	AssertThis(0);
	AssertVarMem(prc);

	_rcSrc = *prc;
	if (_rcSrc.FEmpty())
		{
		_rcSrc.xpRight = _rcSrc.xpLeft + 1;
		_rcSrc.ypBottom = _rcSrc.ypTop + 1;
		}
	AssertThis(0);
}


/***************************************************************************
	Get the destination rectangle.
***************************************************************************/
void GNV::GetRcDst(RC *prc)
{
	AssertThis(0);
	AssertVarMem(prc);
	PT pt;

	*prc = _rcDst;
	_pgpt->GetPtBase(&pt);
	prc->Offset(pt.xp, pt.yp);
}


/***************************************************************************
	Set the destination rectangle.  Also opens up the vis rc and clipping
	and sets default font and pen values.
***************************************************************************/
void GNV::SetRcDst(RC *prc)
{
	AssertThis(0);
	AssertVarMem(prc);
	PT pt;

	_rcDst = *prc;
	if (_rcDst.FEmpty())
		{
		_rcDst.xpRight = _rcDst.xpLeft + 1;
		_rcDst.ypBottom = _rcDst.ypTop + 1;
		}
	_pgpt->GetPtBase(&pt);
	_rcDst.Offset(-pt.xp, -pt.yp);
	_gdd.prcsClip = pvNil;
	_rcVis.Max();

	_dsf.onn = vntl.OnnSystem();
	_dsf.grfont = fontNil;
	_dsf.dyp = vpappb->DypTextDef();
	_dsf.tah = tahLeft;
	_dsf.tav = tavTop;
	_gdd.dxpPen = _gdd.dypPen = 1;

	AssertThis(0);
}


/***************************************************************************
	Set the visible rectangle.  When ClipRc is called, the rc is first
	intersected with the vis rc.  Passing pvNil opens up the vis rectangle
	(so there is no natural clipping).  This should be called _after_
	SetRcDst, since SetRcDst opens up the vis rc.  This also opens the
	clipping to the vis rc.
***************************************************************************/
void GNV::SetRcVis(RC *prc)
{
	AssertThis(0);
	AssertNilOrVarMem(prc);

	if (prc == pvNil)
		{
		_rcVis.Max();
		_gdd.prcsClip = pvNil;
		}
	else
		{
		_rcVis = *prc;
		_rcVis.Map(&_rcSrc, &_rcDst);
		_rcsClip = RCS(_rcVis);
		_gdd.prcsClip = &_rcsClip;
		}
	AssertThis(0);
}


/***************************************************************************
	Intersect the current vis rectangle with the given rectangle and make
	that the new vis rectangle.  Opens the clipping to the vis rectangle
	also.
***************************************************************************/
void GNV::IntersectRcVis(RC *prc)
{
	AssertThis(0);
	AssertVarMem(prc);
	RC rc;

	rc = *prc;
	rc.Map(&_rcSrc, &_rcDst);
	_rcVis.FIntersect(&rc);
	_rcsClip = RCS(_rcVis);
	_gdd.prcsClip = &_rcsClip;
	AssertThis(0);
}


/***************************************************************************
	Set the clipping (in source coordinates).  If prc is pvNil, opens up
	the clipping (to the vis rectangle).  Otherwise, sets the clipping
	to the intersection of the vis rectangle and *prc.
***************************************************************************/
void GNV::ClipRc(RC *prc)
{
	AssertThis(0);
	AssertNilOrVarMem(prc);

	RC rc;

	if (prc == pvNil)
		{
		rc.Max();
		if (rc == _rcVis)
			{
			//no clipping
			_gdd.prcsClip = pvNil;
			return;
			}
		rc = _rcVis;
		}
	else
		{
		rc = *prc;
		rc.Map(&_rcSrc, &_rcDst);
		rc.FIntersect(&_rcVis);
		}

	_rcsClip = RCS(rc);
	_gdd.prcsClip = &_rcsClip;
}


/***************************************************************************
	Clip to the source rectangle.
***************************************************************************/
void GNV::ClipToSrc(void)
{
	AssertThis(0);
	ClipRc(&_rcSrc);
}


/***************************************************************************
	Set the pen size (in source coordinates).
***************************************************************************/
void GNV::SetPenSize(long dxpPen, long dypPen)
{
	AssertThis(0);
	AssertIn(dxpPen, 0, kswMax);
	AssertIn(dypPen, 0, kswMax);
	_gdd.dxpPen = LwMulDivAway(dxpPen, _rcDst.Dxp(), _rcSrc.Dxp());
	_gdd.dypPen = LwMulDivAway(dypPen, _rcDst.Dyp(), _rcSrc.Dyp());
}


/***************************************************************************
	Set the current font info.
***************************************************************************/
void GNV::SetFont(long onn, ulong grfont, long dypFont, long tah, long tav)
{
	AssertThis(0);
	_dsf.onn = onn;
	_dsf.grfont = grfont;
	_dsf.dyp = LwMulDivAway(dypFont, _rcDst.Dyp(), _rcSrc.Dyp());
	_dsf.tah = tah;
	_dsf.tav = tav;
	AssertThis(0);
}


/***************************************************************************
	Set the current font.
***************************************************************************/
void GNV::SetOnn(long onn)
{
	AssertThis(0);
	_dsf.onn = onn;
	AssertThis(0);
}


/***************************************************************************
	Set the current font style.
***************************************************************************/
void GNV::SetFontStyle(ulong grfont)
{
	AssertThis(0);
	_dsf.grfont = grfont;
	AssertThis(0);
}


/***************************************************************************
	Set the current font size.
***************************************************************************/
void GNV::SetFontSize(long dyp)
{
	AssertThis(0);
	_dsf.dyp = LwMulDivAway(dyp, _rcDst.Dyp(), _rcSrc.Dyp());
	AssertThis(0);
}


/***************************************************************************
	Set the current font alignment.
***************************************************************************/
void GNV::SetFontAlign(long tah, long tav)
{
	AssertThis(0);
	_dsf.tah = tah;
	_dsf.tav = tav;
	AssertThis(0);
}


/******************************************************************************
	Set the current font.  Font size must be specified in Dst units.
******************************************************************************/
void GNV::SetDsf(DSF *pdsf)
{
	AssertThis(0);
	AssertPo(pdsf, 0);

	_dsf = *pdsf;
	AssertThis(0);
}


/******************************************************************************
	Get the current font.  Font size is specified in Dst units.
******************************************************************************/
void GNV::GetDsf(DSF *pdsf)
{
	AssertThis(0);
	AssertVarMem(pdsf);
	*pdsf = _dsf;
}


/******************************************************************************
	Draw some text.
******************************************************************************/
void GNV::DrawRgch(achar *prgch, long cch, long xp, long yp, ACR acrFore, ACR acrBack)
{
	AssertThis(0);
	AssertIn(cch, 0, kcbMax);
	AssertPvCb(prgch, cch);
	AssertPo(&acrFore, 0);
	AssertPo(&acrBack, 0);

	PTS pts;

	if (cch == 0)
		return;

	_gdd.acrFore = acrFore;
	_gdd.acrBack = acrBack;
	_MapPtPts(xp, yp, &pts);
	_pgpt->DrawRgch(prgch, cch, pts, &_gdd, &_dsf);
}


/***************************************************************************
	Draw the given string.
***************************************************************************/
void GNV::DrawStn(PSTN pstn, long xp, long yp, ACR acrFore, ACR acrBack)
{
	AssertThis(0);
	AssertPo(pstn, 0);
	AssertPo(&acrFore, 0);
	AssertPo(&acrBack, 0);

	DrawRgch(pstn->Prgch(), pstn->Cch(), xp, yp, acrFore, acrBack);
}


/******************************************************************************
	Return the bounding box of the text.  If the GNV has any scaling, this
	is approximate.  This even works if cch is 0 (just gives the height).
******************************************************************************/
void GNV::GetRcFromRgch(RC *prc, achar *prgch, long cch, long xp, long yp)
{
	AssertThis(0);
	AssertVarMem(prc);
	AssertIn(cch, 0, kcbMax);
	AssertPvCb(prgch, cch);

	PTS pts;
	RCS rcs;

	_MapPtPts(xp, yp, &pts);
	_pgpt->GetRcsFromRgch(&rcs, prgch, cch, pts, &_dsf);
	*prc = rcs;
	prc->Map(&_rcDst, &_rcSrc);
}


/******************************************************************************
	Return the bounding box of the text.  If the GNV has any scaling, this
	is approximate.  This even works if the string is empty (gives the height).
******************************************************************************/
void GNV::GetRcFromStn(RC *prc, PSTN pstn, long xp, long yp)
{
	AssertThis(0);
	AssertVarMem(prc);
	AssertPo(pstn, 0);

	GetRcFromRgch(prc, pstn->Prgch(), pstn->Cch(), xp, yp);
}


/***************************************************************************
	Copy bits from a GNV to this one.
***************************************************************************/
void GNV::CopyPixels(PGNV pgnvSrc, RC *prcSrc, RC *prcDst)
{
	AssertThis(0);
	AssertPo(pgnvSrc, 0);
	AssertVarMem(prcSrc);
	AssertVarMem(prcDst);
	RCS rcsSrc, rcsDst;

	if (!pgnvSrc->_FMapRcRcs(prcSrc, &rcsSrc) || !_FMapRcRcs(prcDst, &rcsDst))
		return;
	_pgpt->CopyPixels(pgnvSrc->_pgpt, &rcsSrc, &rcsDst, &_gdd);
}


ulong _mpgfdgrfpt[4] =
	{
	fptNegateXp,
	fptNil,
	fptNegateYp | fptTranspose,
	fptTranspose
	};


ulong _mpgfdgrfptInv[4] =
	{
	fptNegateXp,
	fptNil,
	fptNegateXp | fptTranspose,
	fptTranspose
	};


/***************************************************************************
	Get the old palette in *ppglclrOld, allocate a transitionary palette
	in *ppglclrTrans, and get init the palette animation.  On failure set
	the new palette and set *ppglclrOld and *ppglclrTrans to nil.
	If cbitPixel is not zero and not the depth of this device, this sets
	the palette and returns false.
***************************************************************************/
bool GNV::_FInitPaletteTrans(PGL pglclr, PGL *ppglclrOld, PGL *ppglclrTrans,
	long cbitPixel)
{
	AssertNilOrPo(pglclr, 0);
	AssertVarMem(ppglclrOld);
	AssertVarMem(ppglclrTrans);
	long cclr = pvNil == pglclr ? 256 : pglclr->IvMac();

	*ppglclrOld = pvNil;
	*ppglclrTrans = pvNil;

	// get the current palette and set up the temporary transitionary palette
	if (0 != cbitPixel && _pgpt->CbitPixel() != cbitPixel ||
			pvNil == (*ppglclrOld = GPT::PglclrGetPalette()) ||
			0 == (cclr = LwMin((*ppglclrOld)->IvMac(), cclr)) ||
			pvNil == (*ppglclrTrans = GL::PglNew(size(CLR), cclr)))
		{
		ReleasePpo(ppglclrOld);
		if (pvNil != pglclr)
			GPT::SetActiveColors(pglclr, fpalIdentity);
		return fFalse;
		}

	AssertDo((*ppglclrTrans)->FSetIvMac(cclr), 0);
	GPT::SetActiveColors(*ppglclrOld, fpalIdentity | fpalInitAnim);
	return fTrue;
}


/***************************************************************************
	Transition the palette.  Merge pglclrOld and pglclrNew into pglclrTrans
	and animate the palette to pglclrTrans.  If either source palette is nil,
	*pclrSub is used in place of the nil palette.  acrSub must be an RGB color.
***************************************************************************/
void GNV::_PaletteTrans(PGL pglclrOld, PGL pglclrNew, long lwNum, long lwDen,
	PGL pglclrTrans, CLR *pclrSub)
{
	AssertNilOrPo(pglclrOld, 0);
	AssertNilOrPo(pglclrNew, 0);
	AssertIn(lwDen, 1, kcbMax);
	AssertIn(lwNum, 0, lwDen + 1);
	AssertNilOrVarMem(pclrSub);

	long iclr;
	CLR clrOld, clrNew;
	CLR clrSub;

	iclr = pglclrTrans->IvMac();
	if (pvNil != pglclrOld)
		iclr = LwMin(pglclrOld->IvMac(), iclr);
	if (pvNil != pglclrNew)
		iclr = LwMin(pglclrNew->IvMac(), iclr);
	if (pvNil != pclrSub)
		clrSub = *pclrSub;
	else
		ClearPb(&clrSub, size(clrSub));

	while (iclr-- > 0)
		{
		clrOld = clrNew = clrSub;
		if (pvNil != pglclrOld)
			pglclrOld->Get(iclr, &clrOld);
		if (pvNil != pglclrNew)
			pglclrNew->Get(iclr, &clrNew);

		clrOld.bRed += (byte)LwMulDiv((long)clrNew.bRed - clrOld.bRed,
			lwNum, lwDen);
		clrOld.bGreen += (byte)LwMulDiv((long)clrNew.bGreen - clrOld.bGreen,
			lwNum, lwDen);
		clrOld.bBlue += (byte)LwMulDiv((long)clrNew.bBlue - clrOld.bBlue,
			lwNum, lwDen);
		pglclrTrans->Put(iclr, &clrOld);
		}

	GPT::SetActiveColors(pglclrTrans, fpalIdentity | fpalAnimate);
}


/***************************************************************************
	Create a temporary GNV that is a copy of the given rectangle in this
	GNV.  This is used for several transitions.
***************************************************************************/
bool GNV::_FEnsureTempGnv(PGNV *ppgnv, RC *prc)
{
	PGPT pgpt;
	PGNV pgnv;

	if (pvNil == (pgpt = GPT::PgptNewOffscreen(prc, 8)) ||
			pvNil == (pgnv = NewObj GNV(pgpt)))
		{
		ReleasePpo(&pgpt);
		*ppgnv = pvNil;
		return fFalse;
		}

	ReleasePpo(&pgpt);
	pgnv->CopyPixels(this, prc, prc);
	GPT::Flush();
	*ppgnv = pgnv;
	return fTrue;
}


/***************************************************************************
	Wipe the source gnv onto this one.  If acrFill is not kacrClear, first
	wipe acrFill on.  The source and destination rectangles must be the same
	size.  gfd indicates which direction the wipe is.  If pglclr is not
	nil and acrFill is clear, the palette transition is gradual.
***************************************************************************/
void GNV::Wipe(long gfd, ACR acrFill, PGNV pgnvSrc, RC *prcSrc, RC *prcDst,
	ulong dts, PGL pglclr)
{
	AssertThis(0);
	AssertPo(&acrFill, 0);
	AssertPo(pgnvSrc, 0);
	AssertVarMem(prcSrc);
	AssertVarMem(prcDst);
	AssertNilOrPo(pglclr, 0);

	ulong grfpt, grfptInv;
	ulong tsStart, dtsT;
	long dxp, dxpTot;
	long cact;
	RC rcSrc, rcDst;
	RC rc1, rc2;
	PGL pglclrOld = pvNil;
	PGL pglclrTrans = pvNil;

	Assert(prcSrc->Dyp() == prcDst->Dyp() && prcSrc->Dxp() == prcDst->Dxp(),
		"rc's are scaled");

	GPT::Flush();
	if (!FIn(dts, 1, kdtsMaxTrans))
		dts = kdtsSecond;

	for (cact = 0; cact < 2; cact++)
		{
		grfpt = _mpgfdgrfpt[gfd & 0x03];
		grfptInv = _mpgfdgrfptInv[gfd & 0x03];
		gfd >>= 2;

		if (cact == 0)
			{
			if (kacrClear == acrFill)
				continue;
			}
		else if (pvNil != pglclr &&
				!_FInitPaletteTrans(pglclr, &pglclrOld, &pglclrTrans, 8))
			{
			pglclr = pvNil; // so we don't try to transition
			}

		tsStart = TsCurrent();
		rcSrc = *prcSrc;
		rcDst = *prcDst;
		rcSrc.Transform(grfpt);
		rcDst.Transform(grfpt);
		dxpTot = rcDst.Dxp();
		for (dxp = 0; dxp < dxpTot; )
			{
			rc1 = rcSrc;
			rc2 = rcDst;
			rc1.xpLeft = rcSrc.xpLeft + dxp;
			rc2.xpLeft = rcDst.xpLeft + dxp;
			dtsT = LwMin(TsCurrent() - tsStart, dts);
			dxp = LwMulDiv(dxpTot, dtsT, dts);
			rc1.xpRight = rcSrc.xpLeft + dxp;
			rc2.xpRight = rcDst.xpLeft + dxp;

			if (cact == 1 && pglclr != pvNil)
				_PaletteTrans(pglclrOld, pglclr, dtsT, dts, pglclrTrans);

			if (!rc2.FEmpty())
				{
				rc1.Transform(grfptInv);
				rc2.Transform(grfptInv);
				if (cact == 0)
					FillRc(&rc2, acrFill);
				else
					CopyPixels(pgnvSrc, &rc1, &rc2);
				GPT::Flush();
				}
			}

		if (pvNil != pglclr)
			{
			// set the palette
			GPT::SetActiveColors(pglclr, fpalIdentity);
			pglclr = pvNil; // so we don't transition during the second wipe
			}
		}

	ReleasePpo(&pglclrOld);
	ReleasePpo(&pglclrTrans);
}


/***************************************************************************
	Slide the source gnv onto this one.  The source and destination
	rectangles must be the same size.
***************************************************************************/
void GNV::Slide(long gfd, ACR acrFill, PGNV pgnvSrc, RC *prcSrc, RC *prcDst,
	ulong dts, PGL pglclr)
{
	AssertThis(0);
	AssertPo(&acrFill, 0);
	AssertPo(pgnvSrc, 0);
	AssertVarMem(prcSrc);
	AssertVarMem(prcDst);
	AssertNilOrPo(pglclr, 0);

	ulong grfpt, grfptInv;
	ulong dtsT, tsStart;
	long cact;
	long dxp, dxpTot, dxpOld;
	RC rcSrc, rcDst;
	RC rc1, rc2;
	PGNV pgnv;
	PT dpt;
	PGL pglclrOld = pvNil;
	PGL pglclrTrans = pvNil;

	Assert(prcSrc->Dyp() == prcDst->Dyp() && prcSrc->Dxp() == prcDst->Dxp(),
		"rc's are scaled");

	// allocate the offscreen port and copy the destination into it.
	if (!_FEnsureTempGnv(&pgnv, prcDst))
		{
		if (pvNil != pglclr)
			GPT::SetActiveColors(pglclr, fpalIdentity);
		CopyPixels(pgnvSrc, prcSrc, prcDst);
		GPT::Flush();
		return;
		}

	if (!FIn(dts, 1, kdtsMaxTrans))
		dts = kdtsSecond;

	for (cact = 0; cact < 2; cact++)
		{
		grfpt = _mpgfdgrfpt[gfd & 0x03];
		grfptInv = _mpgfdgrfptInv[gfd & 0x03];
		gfd >>= 2;

		if (cact == 0)
			{
			if (kacrClear == acrFill)
				continue;
			}
		else if (pvNil != pglclr &&
				!_FInitPaletteTrans(pglclr, &pglclrOld, &pglclrTrans))
			{
			pglclr = pvNil; // so we don't try to transition
			}

		tsStart = TsCurrent();
		rcSrc = *prcSrc;
		rcDst = *prcDst;
		rcSrc.Transform(grfpt);
		rcDst.Transform(grfpt);
		dxpTot = rcDst.Dxp();
		for (dxp = 0; dxp < dxpTot; )
			{
			dxpOld = dxp;
			dtsT = LwMin(TsCurrent() - tsStart, dts);
			dxp = LwMulDiv(dxpTot, dtsT, dts);

			if (dxp != dxpOld)
				{
				// scroll the stuff that's already there
				dpt.xp = dxp - dxpOld;
				dpt.yp = 0;
				dpt.Transform(grfptInv);
				pgnv->ScrollRc(prcDst, dpt.xp, dpt.yp);

				// copy in the new stuff
				rc1 = rcSrc;
				rc2 = rcDst;
				rc1.xpLeft = rc1.xpRight - dxp;
				rc1.xpRight -= dxpOld;
				rc2.xpRight = rc2.xpLeft + dxp - dxpOld;
				rc1.Transform(grfptInv);
				rc2.Transform(grfptInv);
				if (cact == 0)
					pgnv->FillRc(&rc2, acrFill);
				else
					pgnv->CopyPixels(pgnvSrc, &rc1, &rc2);
				}

			if (pvNil != pglclr && 1 == cact)
				_PaletteTrans(pglclrOld, pglclr, dtsT, dts, pglclrTrans);

			if (dxp != dxpOld)
				{
				// copy the result to the destination
				CopyPixels(pgnv, prcDst, prcDst);
				GPT::Flush();
				}
			}

		if (pvNil != pglclr)
			{
			// set the palette
			GPT::SetActiveColors(pglclr, fpalIdentity);

			// if we're not in 8 bit and cact is 1, copy the pixels so we
			// make sure we've drawn the picture after the last palette change
			if (1 == cact && _pgpt->CbitPixel() != 8)
				{
				CopyPixels(pgnv, prcDst, prcDst);
				GPT::Flush();
				}
			pglclr = pvNil; // so we don't transition during the second wipe
			}
		}

	ReleasePpo(&pgnv);
	ReleasePpo(&pglclrOld);
	ReleasePpo(&pglclrTrans);
}



// klwPrime must be a prime and klwRoot must be a primitive root for klwPrime.
// the code under #ifdef SPECIAL_PRIME below assumes that klwPrime is
// (2^16 + 1) and klwRoot is (2 ^15 - 1).  If you change klwPrime and/or
// klwRoot, make sure that SPECIAL_PRIME is not defined.

#define SPECIAL_PRIME
#define klwPrime 65537 // a prime
#define klwRoot  32767 // a primitive root for the prime


/***************************************************************************
	Returns the next quasi-random number for Dissolve.
***************************************************************************/
inline long _LwNextDissolve(long lw)
{
	AssertIn(lw, 1, klwPrime);

#ifdef SPECIAL_PRIME

	Assert(klwPrime == 0x00010001, 0);
	Assert(klwRoot == 0x00007FFF, 0);

	// multiply by 2^15 - 1
	lw = (lw << 15) - lw;

	// mod by 2^16 + 1
	lw = (lw & 0x0000FFFFL) - (long)((ulong)lw >> 16);
	if (lw < 0)
		lw += klwPrime;

#else //!SPECIAL_PRIME

	LwMulDivMod(lw, klwRoot, klwPrime, &lw);

#endif //!SPECIAL_PRIME

	return lw;
}


/***************************************************************************
	Dissolve the source gnv into this one.  If acrFill is not kacrClear,
	first dissolve into a solid acrFill, then into the source. The source
	and destination rectangles must be the same size.  If pgnvSrc is nil,
	just dissolve into the solid color.  Each portion is done in dts time.
***************************************************************************/
void GNV::Dissolve(long crcWidth, long crcHeight, ACR acrFill,
	PGNV pgnvSrc, RC *prcSrc, RC *prcDst, ulong dts, PGL pglclr)
{
	AssertThis(0);
	AssertPo(&acrFill, 0);
	AssertNilOrPo(pgnvSrc, 0);
	AssertNilOrVarMem(prcSrc);
	AssertVarMem(prcDst);
	AssertNilOrPo(pglclr, 0);
	AssertIn(crcWidth, 0, kcbMax);
	AssertIn(crcHeight, 0, kcbMax);

	ulong tsStart, dtsT;
	byte bFill;
	long cbRowSrc, cbRowDst;
	RND rnd;
	long lw, cact, irc, crc, crcFill, crcT;
	RC rc1, rc2;
	bool fOnScreen;
	long dibExtra, dibRow, ibExtra;
	byte *pbRow;
	byte *prgbDst = pvNil;
	byte *prgbSrc = pvNil;
	PGNV pgnv = pvNil;
	PGL pglclrOld = pvNil;
	PGL pglclrTrans = pvNil;

	if (prcDst->FEmpty())
		return;

	if (crcWidth <= 0 || crcHeight <= 0)
		{
		// do off screen pixel level dissolve
		fOnScreen = fFalse;

		// allocate the offscreen port and copy the destination into it.
		if (pgnvSrc != pvNil)
			{
			PGPT pgptSrc;

			AssertVarMem(prcSrc);
			Assert(prcSrc->Dyp() == prcDst->Dyp() && prcSrc->Dxp() == prcDst->Dxp(),
				"rc's are scaled");
			pgptSrc = pgnvSrc->Pgpt();
			if (pgptSrc->CbitPixel() != 8 ||
					pvNil == (prgbSrc = pgptSrc->PrgbLockPixels(&rc2)))
				{
				Bug("Can't dissolve from this GPT");
				goto LFail;
				}
			rc1 = *prcSrc;
			rc1.Map(&pgnvSrc->_rcSrc, &pgnvSrc->_rcDst);
			if (!rc2.FContains(&rc1))
				{
				Bug("Source rectangle is outside the source bitmap");
				goto LFail;
				}
			cbRowSrc = pgptSrc->CbRow();
			prgbSrc += LwMul(rc1.ypTop - rc2.ypTop, cbRowSrc) +
				rc1.xpLeft - rc2.xpLeft;
			}

		if (!_FEnsureTempGnv(&pgnv, prcDst))
			{
LFail:
			if (pvNil != prgbSrc && pvNil != pgnvSrc)
				pgnvSrc->Pgpt()->Unlock();

			if (pvNil != pglclr)
				GPT::SetActiveColors(pglclr, fpalIdentity);
			if (pvNil != pgnvSrc)
				CopyPixels(pgnvSrc, prcSrc, prcDst);
			else
				FillRc(prcDst, acrFill);
			GPT::Flush();
			return;
			}
		prgbDst = pgnv->Pgpt()->PrgbLockPixels();
		cbRowDst = pgnv->Pgpt()->CbRow();

		if (acrFill != kacrClear)
			{
			// get the byte value to fill with
			byte bT = prgbDst[0];
	
			rc1.Set(prcDst->xpLeft, prcDst->ypTop,
				prcDst->xpLeft + 1, prcDst->ypTop + 1);
			pgnv->FillRc(&rc1, acrFill);
			GPT::Flush();
			bFill = prgbDst[0];
			prgbDst[0] = bT;
			}

		crcWidth = cbRowDst;
		crcHeight = prcDst->Dyp();
		crcFill = LwMul(crcWidth, crcHeight) + prcDst->Dxp() - cbRowDst;

		dibExtra = (klwPrime - 1) % crcWidth;
		dibRow = ((klwPrime - 1) / crcWidth) * cbRowSrc;
		}
	else
		{
		// on screen dissolve
		fOnScreen = fTrue;
		crcWidth = LwMin(prcDst->Dxp(), crcWidth);
		crcHeight = LwMin(prcDst->Dyp(), crcHeight);
		crcFill = LwMul(crcWidth, crcHeight);
		}

	if (!FIn(dts, 1, kdtsMaxTrans))
		dts = kdtsSecond;

	// the first time through, dissolve to the color; the second time
	// through dissolve to the source bitmap
	for (cact = 0; cact < 2; cact++)
		{
		if (cact == 0)
			{
			if (kacrClear == acrFill)
				continue;
			}
		else
			{
			if (pvNil == pgnvSrc)
				goto LSetPalette;
			if (pvNil != pglclr && !_FInitPaletteTrans(pglclr, &pglclrOld,
					&pglclrTrans, fOnScreen ? 8 : 0))
				{
				pglclr = pvNil; // so we don't try to transition
				}
			}

		// We start with lw a random value between 1 and (klwPrime - 1)
		// (inclusive). Subsequent values of lw are computed as
		// lw = lw * klwRoot % klwPrime. Because klwRoot is a primitive root
		// of unity for klwPrime, lw will take on all values from 1 thru
		// (klwPrime - 1) in seemingly random order.

		irc = rnd.LwNext(crcFill);
		lw = (crcFill - irc - 1) % (klwPrime - 1) + 1;

		if (!fOnScreen && cact != 0)
			{
			// pbRow points to the row of prgbSrc that the current source
			// pixel is in.  ibExtra is the offset of the source pixel
			// into the row (the "xp" coordinate of the source pixel).
			// dibRow and dibExtra are for finding the source pixel
			// incrementally.  When we subtract (klwPrime - 1) from irc,
			// we subtract dibRow from pbRow and dibExtra from ibExtra.
			// If ibExtra goes negative, we add cbRowSrc to pbRow and
			// crcWidth to ibExtra.

			ibExtra = irc % crcWidth;
			pbRow = prgbSrc + (irc / crcWidth) * cbRowSrc;
			}

		tsStart = TsCurrent();
		for (crc = 0; crc < crcFill; )
			{
			dtsT = LwMin(TsCurrent() - tsStart, dts);
			crcT = LwMulDiv(crcFill, dtsT, dts) - crc;
			if (crcT <= 0)
				goto LPaletteTrans;
			crc += crcT;

			if (fOnScreen)
				{
				for ( ; crcT > 0; crcT--)
					{
					// find the next rectangle to fill
					for (irc -= klwPrime - 1; irc < 0; irc = crcFill - lw)
						lw = _LwNextDissolve(lw);

					rc1.SetToCell(prcDst, crcWidth, crcHeight, irc % crcWidth,
						irc / crcWidth);
					if (cact == 0)
						FillRc(&rc1, acrFill);
					else
						{
						rc2 = rc1;
						rc2.Map(prcDst, prcSrc);
						CopyPixels(pgnvSrc, &rc2, &rc1);
						}
					}
				goto LPaletteTrans;
				}

			if (cact == 0)
				{
				// fill with bFill
				for ( ; crcT > 0; crcT--)
					{
					// find the next pixel to fill
					for (irc -= klwPrime - 1; irc < 0; irc = crcFill - lw)
						lw = _LwNextDissolve(lw);
					prgbDst[irc] = bFill;
					}
				goto LBlastToScreen;
				}

			// cact == 1, fill from prgbSrc
			for ( ; crcT > 0; crcT--)
				{
				// find the next rectangle to fill
				irc -= klwPrime - 1;
				if (irc >= 0)
					{
					pbRow -= dibRow;
					ibExtra -= dibExtra;
					if (ibExtra < 0)
						{
						pbRow -= cbRowSrc;
						ibExtra += crcWidth;
						}
					}
				else
					{
					do
						{
						lw = _LwNextDissolve(lw);
						}
					while (0 > (irc = crcFill - lw));

#ifdef IN_80386
					__asm
						{
						// ibExtra = irc % crcWidth;
						// pbRow = prgbSrc + (irc / crcWidth) * cbRowSrc;
						mov		edx,irc
						movzx	eax,dx
						shr		edx,16
						div		WORD PTR crcWidth	// 16 bit divide for speed
						imul	eax,cbRowSrc
						mov		ibExtra,edx
						add		eax,prgbSrc
						mov		pbRow,eax
						}
#else //!IN_80386
					ibExtra = irc % crcWidth;
					pbRow = prgbSrc + (irc / crcWidth) * cbRowSrc;
#endif //!IN_80386
					}

				prgbDst[irc] = pbRow[ibExtra];
				}

LBlastToScreen:
			CopyPixels(pgnv, prcDst, prcDst);
			GPT::Flush();

LPaletteTrans:
			if (cact == 1 && pglclr != pvNil)
				_PaletteTrans(pglclrOld, pglclr, dtsT, dts, pglclrTrans);
			}

LSetPalette:
		if (pvNil != pglclr)
			{
			// set the palette
			GPT::SetActiveColors(pglclr, fpalIdentity);

			// if we're not in 8 bit and cact is 1, copy the pixels so we
			// make sure we've drawn the picture after the last palette change
			if (pvNil != pgnv && 1 == cact && _pgpt->CbitPixel() != 8)
				{
				CopyPixels(pgnv, prcDst, prcDst);
				GPT::Flush();
				}
			pglclr = pvNil; // so we don't transition during the second wipe
			}
		}

	if (pvNil != prgbDst)
		pgnv->Pgpt()->Unlock();
	if (pvNil != prgbSrc && pvNil != pgnvSrc)
		pgnvSrc->Pgpt()->Unlock();
	ReleasePpo(&pgnv);
	ReleasePpo(&pglclrOld);
	ReleasePpo(&pglclrTrans);
}


/***************************************************************************
	Fade the palette to color acrFade, copy the pixels from pgnvSrc to
	this gnv, then fade to the new palette or original palette.  Each fade
	is given dts time.  Asserts that acrFade is an rgb color.  cactMax is
	the maximum number of palette interpolations to do.  It doesn't make
	sense for this to be bigger than 256.  If it's zero, we'll use 256.
***************************************************************************/
void GNV::Fade(long cactMax, ACR acrFade, PGNV pgnvSrc, RC *prcSrc, RC *prcDst,
	ulong dts, PGL pglclr)
{
	AssertThis(0);
	AssertIn(cactMax, 0, 257);
	AssertPo(&acrFade, facrRgb);
	AssertPo(pgnvSrc, 0);
	AssertVarMem(prcSrc);
	AssertVarMem(prcDst);
	AssertNilOrPo(pglclr, 0);

	ulong tsStart;
	long cact, cactOld;
	CLR clr;
	PGL pglclrOld = pvNil;
	PGL pglclrTrans = pvNil;

	cactMax = (cactMax <= 0) ? 256 : LwMin(cactMax, 256);

	if (!_FInitPaletteTrans(pglclr, &pglclrOld, &pglclrTrans, 8))
		{
		CopyPixels(pgnvSrc, prcSrc, prcDst);
		GPT::Flush();
		return;
		}

	GPT::Flush();

	if (!FIn(dts, 1, kdtsMaxTrans))
		dts = kdtsSecond;

	acrFade.GetClr(&clr);

	tsStart = TsCurrent();
	for (cact = 0; cact < cactMax; )
		{
		cactOld = cact;
		cact = LwMulDiv(cactMax, LwMin(TsCurrent() - tsStart, dts), dts);
		if (cactOld < cact)
			_PaletteTrans(pglclrOld, pvNil, cact, cactMax, pglclrTrans, &clr);
		}

	CopyPixels(pgnvSrc, prcSrc, prcDst);
	GPT::Flush();

	if (pvNil == pglclr)
		pglclr = pglclrOld;

	tsStart = TsCurrent();
	for (cact = 0; cact < cactMax; )
		{
		cactOld = cact;
		cact = LwMulDiv(cactMax, LwMin(TsCurrent() - tsStart, dts), dts);
		if (cactOld < cact)
			_PaletteTrans(pvNil, pglclr, cact, cactMax, pglclrTrans, &clr);
		}

	GPT::SetActiveColors(pglclr, fpalIdentity);
	ReleasePpo(&pglclrOld);
	ReleasePpo(&pglclrTrans);
}


/***************************************************************************
	Open and/or close a rectangular iris onto the gnvSrc with an
	intermediate color of acrFill (if not clear).  xp, yp are the focus
	point of the iris (in destination coordinates).
***************************************************************************/
void GNV::Iris(long gfd, long xp, long yp, ACR acrFill,
	PGNV pgnvSrc, RC *prcSrc, RC *prcDst, ulong dts, PGL pglclr)
{
	AssertThis(0);
	AssertPo(&acrFill, 0);
	AssertPo(pgnvSrc, 0);
	AssertVarMem(prcSrc);
	AssertVarMem(prcDst);
	AssertNilOrPo(pglclr, 0);

	ulong tsStart, dtsT;
	RC rc, rcOld, rcDst;
	PT pt, ptBase;
	long cact;
	bool fOpen;
	PREGN pregn, pregnClip;
	PGL pglclrOld = pvNil;
	PGL pglclrTrans = pvNil;

	GPT::Flush();

	if (pvNil == (pregn = REGN::PregnNew(prcDst)))
		goto LFail;

	if (!FIn(dts, 1, kdtsMaxTrans))
		dts = kdtsSecond;

	_pgpt->GetPtBase(&ptBase);

	pt.xp = LwBound(xp, prcDst->xpLeft, prcDst->xpRight + 1);
	pt.yp = LwBound(yp, prcDst->ypTop, prcDst->ypBottom + 1);
	pt.Map(&_rcSrc, &_rcDst);
	pt += ptBase;
	rcDst = *prcDst;
	rcDst.Map(&_rcSrc, &_rcDst);
	rcDst.Offset(ptBase.xp, ptBase.yp);

	for (cact = 0; cact < 2; cact++)
		{
		if (cact == 0)
			{
			if (kacrClear == acrFill)
				continue;
			}
		else if (pvNil != pglclr &&
				!_FInitPaletteTrans(pglclr, &pglclrOld, &pglclrTrans, 8))
			{
			pglclr = pvNil; // so we don't try to transition
			}

		fOpen = !(gfd & (1 << cact));
		if (fOpen)
			rc.Set(pt.xp, pt.yp, pt.xp, pt.yp);
		else
			rc = rcDst;

		pregnClip = pvNil;
		tsStart = TsCurrent();
		for (dtsT = 0; dtsT < dts; )
			{
			rcOld = rc;
			dtsT = LwMin(TsCurrent() - tsStart, dts);
			if (!fOpen)
				dtsT = dts - dtsT;
			rc.xpLeft = pt.xp + LwMulDiv(rcDst.xpLeft - pt.xp, dtsT, dts);
			rc.xpRight = pt.xp + LwMulDiv(rcDst.xpRight - pt.xp, dtsT, dts);
			rc.ypTop = pt.yp + LwMulDiv(rcDst.ypTop - pt.yp, dtsT, dts);
			rc.ypBottom = pt.yp + LwMulDiv(rcDst.ypBottom - pt.yp, dtsT, dts);
			if (!fOpen)
				dtsT = dts - dtsT;

			if (cact == 1 && pglclr != pvNil)
				_PaletteTrans(pglclrOld, pglclr, dtsT, dts, pglclrTrans);

			if (rc == rcOld)
				continue;

			_pgpt->ClipToRegn(&pregnClip);
			pregn->SetRc(fOpen ? &rc : &rcOld);
			if (!pregn->FDiffRc(fOpen ? &rcOld : &rc) ||
					pvNil != pregnClip && !pregn->FIntersect(pregnClip))
				{
				_pgpt->ClipToRegn(&pregnClip);
				ReleasePpo(&pregn);
LFail:
				if (pvNil != pglclr)
					GPT::SetActiveColors(pglclr, fpalIdentity);
				CopyPixels(pgnvSrc, prcSrc, prcDst);
				return;
				}
			_pgpt->ClipToRegn(&pregnClip);

			_pgpt->ClipToRegn(&pregn);
			if (cact == 0)
				FillRc(prcDst, acrFill);
			else
				CopyPixels(pgnvSrc, prcSrc, prcDst);
			GPT::Flush();
			_pgpt->ClipToRegn(&pregn);
			}

		if (pvNil != pglclr)
			{
			// set the palette
			GPT::SetActiveColors(pglclr, fpalIdentity);
			pglclr = pvNil; // so we don't transition during the second iris
			}
		}

	ReleasePpo(&pregn);
	ReleasePpo(&pglclrOld);
	ReleasePpo(&pglclrTrans);
}


/***************************************************************************
	Draw the picture in the given rectangle.
***************************************************************************/
void GNV::DrawPic(PPIC ppic, RC *prc)
{
	AssertThis(0);
	AssertPo(ppic, 0);
	AssertVarMem(prc);
	RCS rcs;

	if (!_FMapRcRcs(prc, &rcs))
		return;
	_pgpt->DrawPic(ppic, &rcs, &_gdd);
}


/***************************************************************************
	Draw the mbmp with reference point at the given point.
***************************************************************************/
void GNV::DrawMbmp(PMBMP pmbmp, long xp, long yp)
{
	AssertThis(0);
	AssertPo(pmbmp, 0);
	RC rc;
	RCS rcs;

	pmbmp->GetRc(&rc);
	rc.Offset(xp - rc.xpLeft, yp - rc.ypTop);
	if (!_FMapRcRcs(&rc, &rcs))
		return;
	_pgpt->DrawMbmp(pmbmp, &rcs, &_gdd);
}


/***************************************************************************
	Draw the mbmp in the given rectangle.
***************************************************************************/
void GNV::DrawMbmp(PMBMP pmbmp, RC *prc)
{
	AssertThis(0);
	AssertPo(pmbmp, 0);
	AssertVarMem(prc);
	RCS rcs;

	if (!_FMapRcRcs(prc, &rcs))
		return;
	_pgpt->DrawMbmp(pmbmp, &rcs, &_gdd);
}


/***************************************************************************
	Map a rectangle to a system rectangle.  Return true iff the result
	is non-empty.
***************************************************************************/
bool GNV::_FMapRcRcs(RC *prc, RCS *prcs)
{
	AssertThis(0);
	AssertVarMem(prc);
	AssertVarMem(prcs);
	RC rc = *prc;

	rc.Map(&_rcSrc, &_rcDst);
	*prcs = RCS(rc);
	return prcs->left < prcs->right && prcs->top < prcs->bottom;
}


/***************************************************************************
	Map an (xp, yp) pair to a system point.
***************************************************************************/
void GNV::_MapPtPts(long xp, long yp, PTS *ppts)
{
	AssertThis(0);
	AssertVarMem(ppts);
	PT pt(xp, yp);

	pt.Map(&_rcSrc, &_rcDst);
	*ppts = PTS(pt);
}


#ifdef MAC
/***************************************************************************
	Set the port associated with the GNV as the current port and set the
	clipping as in the GNV and set the pen and fore/back color to defaults.
***************************************************************************/
void GNV::Set(void)
{
	AssertThis(0);
	_pgpt->Set(_gdd.prcsClip);
	ForeColor(blackColor);
	BackColor(whiteColor);
	PenNormal();
}


/***************************************************************************
	Restore the port.  Balances a call to GNV::Set.
***************************************************************************/
void GNV::Restore(void)
{
	_pgpt->Restore();
}
#endif //MAC


/***************************************************************************
	Clip to the region specified by *ppregn.  *ppregn is set to the previous
	clip region (may be pvNil).  The GPT takes over ownership of the region
	and relinquishes ownership of the old region.
***************************************************************************/
void GPT::ClipToRegn(PREGN *ppregn)
{
	if (_ptBase.xp != 0 || _ptBase.yp != 0)
		{
		if (pvNil != *ppregn)
			(*ppregn)->Offset(-_ptBase.xp, -_ptBase.yp);
		if (pvNil != _pregnClip)
			_pregnClip->Offset(_ptBase.xp, _ptBase.yp);
		}
	SwapVars(&_pregnClip, ppregn);
	_fNewClip = fTrue;
}


/***************************************************************************
	Set the base PT for the GPT.  This affects the mapping of any attached
	GNVs.
***************************************************************************/
void GPT::SetPtBase(PT *ppt)
{
	AssertThis(0);
	AssertVarMem(ppt);
	_ptBase = *ppt;
}


/***************************************************************************
	Get the base PT for the GPT.
***************************************************************************/
void GPT::GetPtBase(PT *ppt)
{
	AssertThis(0);
	AssertVarMem(ppt);
	*ppt = _ptBase;
}


#ifdef DEBUG
/***************************************************************************
	Mark memory for the GPT.
***************************************************************************/
void GPT::MarkMem(void)
{
	AssertValid(0);
	GPT_PAR::MarkMem();
	MarkMemObj(_pregnClip);
}


/******************************************************************************
	Assert the validity of a polygon descriptor.
******************************************************************************/
void OLY::AssertValid(ulong grf)
{
	AssertThisMem();
	AssertPvCb(rgpts, LwMul(Cpts(), size(PTS)));
}


/******************************************************************************
	Assert the validity of the font description.
******************************************************************************/
void DSF::AssertValid(ulong grf)
{
	AssertThisMem();
	AssertIn(dyp, 1, kswMax);
	AssertVar(vntl.FValidOnn(onn), "Invalid onn", &onn);
	AssertIn(tah, 0, tahLim);
	AssertIn(tav, 0, tavLim);
}
#endif //DEBUG


/******************************************************************************
	Initialize the graphics module.
******************************************************************************/
bool FInitGfx(void)
{
	return vntl.FInit();
}


/***************************************************************************
	Construct a new font list.
***************************************************************************/
NTL::NTL(void)
{
	_pgst = pvNil;
}


/***************************************************************************
	Destroy a font list.
***************************************************************************/
NTL::~NTL(void)
{
	ReleasePpo(&_pgst);
}


#ifdef DEBUG
/***************************************************************************
	Assert the validity of the font list.
***************************************************************************/
void NTL::AssertValid(ulong grf)
{
	NTL_PAR::AssertValid(0);
	AssertPo(_pgst, 0);
}


/***************************************************************************
	Mark memory for the font table.
***************************************************************************/
void NTL::MarkMem(void)
{
	AssertValid(0);
	NTL_PAR::MarkMem();
	MarkMemObj(_pgst);
}


/***************************************************************************
	Return whether the font number is valid.
***************************************************************************/
bool NTL::FValidOnn(long onn)
{
	return pvNil != _pgst && onn >= 0 && onn < _pgst->IstnMac();
}
#endif //DEBUG


/***************************************************************************
	Find the name of the given font.
***************************************************************************/
void NTL::GetStn(long onn, PSTN pstn)
{
	AssertThis(0);
	AssertPo(pstn, 0);

	_pgst->GetStn(onn, pstn);
}


/***************************************************************************
	Get the font number for the given font name.
***************************************************************************/
bool NTL::FGetOnn(PSTN pstn, long *ponn)
{
	AssertThis(0);
	AssertPo(pstn, 0);

	return _pgst->FFindStn(pstn, ponn, fgstUserSorted);
}


/***************************************************************************
	Map a font name to an existing font.  Use platform information if
	possible.
	REVIEW shonk: implement font mapping for real.
***************************************************************************/
long NTL::OnnMapStn(PSTN pstn, short osk)
{
	AssertThis(0);
	AssertPo(pstn, 0);
	long onn;

	if (!FGetOnn(pstn, &onn))
		onn = _onnSystem;
	return onn;
}


/***************************************************************************
	Return the font number mac.
***************************************************************************/
long NTL::OnnMac(void)
{
	AssertThis(0);
	return _pgst->IstnMac();
}


/***************************************************************************
	Create a new polygon by tracing the outline of this one with a
	convex polygon.
***************************************************************************/
POGN OGN::PognTraceOgn(POGN pogn, ulong grfogn)
{
	AssertThis(0);
	AssertPo(pogn, 0);

	POGN pognNew = PognTraceRgpt(pogn->PrgptLock(), pogn->IvMac(), grfogn);

	pogn->Unlock();
	return pognNew;
}


/***************************************************************************
	Create a new polygon by tracing the outline of this one with a
	convex polygon.
***************************************************************************/
POGN OGN::PognTraceRgpt(PT *prgpt, long cpt, ulong grfogn)
{
	AssertThis(0);
	AssertIn(cpt, 2, kcbMax);
	AssertPvCb(prgpt, LwMul(cpt, size(PT)));

	PT *prgptThis;
	AEI aei;
	long iptLast = IvMac() - 1;

	if (2 > cpt || iptLast < 0)
		return pvNil;

	aei.prgpt = prgpt;
	aei.cpt = cpt;
	if (pvNil == (aei.pogn = PognNew(IvMac() * 2 + cpt)))
		return pvNil;
	aei.pogn->SetMinGrow(cpt);

	prgptThis = PrgptLock();
	if (iptLast == 0 || prgptThis[0] == prgptThis[1])
		{
		//if all the points of the polygon are the same, doing this ensures
		//that we will encircle the pen
		aei.ptCur = prgpt[0] + prgptThis[0];
		if (!aei.pogn->FPush(&aei.ptCur))
			goto LError;
		aei.iptPenCur = 1;
		}
	else
		{
		aei.iptPenCur = IptFindLeftmost(prgpt, cpt,
			prgptThis[0].xp - prgptThis[1].xp,
			prgptThis[0].yp - prgptThis[1].yp);
		}
	aei.ptCur = prgpt[aei.iptPenCur] + prgptThis[0];
	if (!aei.pogn->FPush(&aei.ptCur))
		goto LError;

	// Walk to end, adding vertices.
	for (aei.dipt = 1, aei.ipt = 0; aei.ipt < iptLast; aei.ipt++)
		{
		if (!_FAddEdge(&aei))
			goto LError;
		}
	if (fognAutoClose & grfogn)
		{
		if (!_FAddEdge(&aei))
			goto LError;
		aei.ipt = 0;
		aei.dipt = iptLast;
		if (!_FAddEdge(&aei))
			goto LError;
		}

	// Walk back to start.
	for (aei.dipt = aei.ipt = iptLast; aei.ipt > 0; aei.ipt--)
		{
		if (!_FAddEdge(&aei))
			{
LError:
			ReleasePpo(&aei.pogn);
			break;
			}
		}

	Unlock();
	if (pvNil != aei.pogn)
		aei.pogn->FEnsureSpace(0, fgrpShrink);
	return aei.pogn;
}


/***************************************************************************
	Add the vertices encountered while walking an edge of the input polygon.
***************************************************************************/
bool OGN::_FAddEdge(AEI *paei)
{
	AssertVarMem(paei);
	AssertIn(paei->cpt, 1, kcbMax);
	AssertPvCb(paei->prgpt, LwMul(paei->cpt, size(PT)));
	AssertPo(paei->pogn, 0);
	AssertIn(paei->dipt, LwMin(IvMac() - 1, 1), LwMax(IvMac(), 2));
	AssertIn(paei->ipt , 0, IvMac());
	AssertIn(paei->iptPenCur, 0, paei->cpt);

	PT * prgptThis = (PT *)QvGet(0); // Already locked in PognTraceRgpt().
	long iptEnd = (paei->ipt + paei->dipt) % IvMac();
	long iptPenNew = IptFindLeftmost(paei->prgpt, paei->cpt,
		prgptThis[iptEnd].xp - prgptThis[paei->ipt].xp,
		prgptThis[iptEnd].yp - prgptThis[paei->ipt].yp);

	// Add vertices from current to leftmost.
	if (paei->iptPenCur != iptPenNew)
		{
		PT pt;
		long ipt = paei->iptPenCur;
		PT dpt = paei->ptCur - paei->prgpt[ipt];

		do
			{
			ipt = (ipt + 1) % paei->cpt;
			pt = paei->prgpt[ipt] + dpt;
			if (!paei->pogn->FPush(&pt))
				return fFalse;
			}
		while (ipt != iptPenNew);
		}

	// Add vertex at endpoint.
	paei->iptPenCur = iptPenNew;
	paei->ptCur = prgptThis[iptEnd] + paei->prgpt[iptPenNew];
	return paei->pogn->FPush(&paei->ptCur);
}


/***************************************************************************
	Find the leftmost vertex of the rgpt looking down the vector.
	dxp, dyp : Direction of vector to look down.
***************************************************************************/
long IptFindLeftmost(PT *prgpt, long cpt, long dxp, long dyp)
{
	AssertPvCb(prgpt, LwMul(cpt, size(PT)));
	AssertIn(cpt, 2, kcbMax);

	long ipt, iptLeftmost;
	long dzpMac; // Maximum cross product (z vector) found.
	long dzp;

	//reduces the chance of overflow
	if (1 < (dzp = LwGcd(dxp, dyp)))
		{
		dxp /= dzp;
		dyp /= dzp;
		}

	for (dzpMac = klwMin, ipt = 0; ipt < cpt; ipt++)
		{
		dzp = LwMul(dyp, prgpt[ipt].xp) - LwMul(dxp, prgpt[ipt].yp);
		if (dzp > dzpMac)
			{
			dzpMac = dzp;
			iptLeftmost = ipt;
			}
		}
	return iptLeftmost;
}


/***************************************************************************
	-- Allocate a new OGN and ensure that it has space for cptInit elements.
***************************************************************************/
POGN OGN::PognNew(long cptInit)
{
	AssertIn(cptInit, 0, kcbMax);
	POGN pogn;

	if (pvNil == (pogn = NewObj OGN()))
		return pvNil;
	if (cptInit > 0 && !pogn->FEnsureSpace(cptInit, fgrpNil))
		{
		ReleasePpo(&pogn);
		return pvNil;
		}
	AssertPo(pogn, 0);
	return pogn;
}


/***************************************************************************
	Constructor for OGN.
***************************************************************************/
OGN::OGN(void) : GL(size(PT))
{
	AssertThis(0);
}


/***************************************************************************
	This does a 2x stretch blt, clipped to prcClip and pregnClip. The
	clipping is expressed in destination coordinates.
***************************************************************************/
void DoubleStretch(byte *prgbSrc, long cbRowSrc, long dypSrc, RC *prcSrc,
	byte *prgbDst, long cbRowDst, long dypDst, long xpDst, long ypDst,
	RC *prcClip, PREGN pregnClip)
{
	AssertPvCb(prgbSrc, LwMul(cbRowSrc, dypSrc));
	AssertPvCb(prgbDst, LwMul(cbRowDst, dypDst));
	AssertVarMem(prcSrc);
	Assert(prcSrc->xpLeft >= 0 && prcSrc->ypTop >= 0 &&
		prcSrc->xpRight <= cbRowSrc && prcSrc->ypBottom <= dypSrc,
		"Source rectangle not in source bitmap!");
	AssertNilOrVarMem(prcClip);
	AssertNilOrPo(pregnClip, 0);

	long xpOn, xpOff, dypAdvance, dxpBase, yp;
	bool fSecondRow;
	REGSC regsc;
	RC rcT(xpDst, ypDst, xpDst + 2 * prcSrc->Dxp(), ypDst + 2 * prcSrc->Dyp());
	RC rcClip(0, 0, cbRowDst, dypDst);

	if (!rcClip.FIntersect(&rcT))
		return;
	if (pvNil != prcClip && !rcClip.FIntersect(prcClip))
		return;

	// Set up the region scanner
	if (pvNil != pregnClip)
		regsc.Init(pregnClip, &rcClip);
	else
		regsc.InitRc(&rcClip, &rcClip);
	dxpBase = rcClip.xpLeft - xpDst;

	// move to rcClip.ypTop
	yp = rcClip.ypTop;
	prgbSrc += prcSrc->xpLeft +
		LwMul(prcSrc->ypTop + ((yp - ypDst) >> 1), cbRowSrc);
	prgbDst += xpDst + LwMul(yp, cbRowDst);
	fSecondRow = (yp - ypDst) & 1;

	for (;;)
		{
		if (klwMax == (xpOn = regsc.XpCur()))
			{
			//empty strip of the region
			dypAdvance = regsc.DypCur();
			goto LAdvance;
			}
		xpOn += dxpBase;
		if (fSecondRow || (regsc.DypCur() == 1))
			goto LOneRow;

		// copy two rows to the destination
		for (;;)
			{
			xpOff = regsc.XpFetch() + dxpBase;
			AssertIn(xpOff - 1, xpOn, rcClip.Dxp() + dxpBase);

#ifdef IN_80386
			// copy two rows to the destination in native
			#define pbDstReg edi
			#define pbSrcReg esi
			#define pbDst2Reg ebx
			#define lwTReg edx

			__asm
				{
				// lwTReg = xpOn;
				// pbDstReg = prgbDst + xpOn;
				// pbSrcReg = prgbSrc + (xpOn >> 1);
				// pbDst2Reg = pbDstReg + cbRowDst;
				mov		lwTReg,xpOn
				mov		pbSrcReg,lwTReg
				mov		pbDstReg,lwTReg
				sar		pbSrcReg,1
				add		pbDstReg,prgbDst
				add		pbSrcReg,prgbSrc
				mov		pbDst2Reg,pbDstReg
				add		pbDst2Reg,cbRowDst

				// if (!(lwTReg & 1)) goto LGetCount2;
				test	lwTReg,1
				mov		ecx,xpOff
				jz		LGetCount2

				// move the single leading byte
				// lwTReg++;
				// al = *pbSrcReg++;
				// *pbDstReg++ = al;
				// *pbDstReg2++ = al;
				inc		lwTReg
				mov		al,[pbSrcReg]
				inc		pbSrcReg
				mov		[pbDstReg],al
				inc		pbDstReg
				mov		[pbDst2Reg],al
				inc		pbDst2Reg

LGetCount2:
				// ecx = xpOff - lwTReg;
				// ecx >>= 1;
				// if (ecx <= 0) goto LLastByte2;
				sub		ecx,lwTReg
				sar		ecx,1
				jle		LLastByte2

LLoop2:
				// al = *pbSrcReg++;
				// ah = al;
				// *(short *)pbDstReg = ax;
				// *(short *)pbDst2Reg = ax;
				// pbDstReg += 2;
				// pbDst2Reg += 2;
				mov		al,[pbSrcReg]
				inc		pbSrcReg
				mov		ah,al
				mov		[pbDstReg],ax
				add		pbDstReg,2
				mov		[pbDst2Reg],ax
				add		pbDst2Reg,2

				// if (--ecx != 0) goto LLoop2;
				loop	LLoop2

LLastByte2:
				// if (!(xpOff & 1)) goto LDone2;
				test	xpOff,1
				jz		LDone2

				// move the single trailing byte
				// al = *pbSrcReg;
				// *pbDstReg = al;
				// *pbDstReg2 = al;
				mov		al,[pbSrcReg]
				mov		[pbDstReg],al
				mov		[pbDst2Reg],al
LDone2:
				}

			#undef pbDstReg
			#undef pbSrcReg
			#undef pbDst2Reg
			#undef lwTReg
#else //!IN_80386
			// copy two rows to the destination in C code
			byte *pbSrc = prgbSrc + (xpOn >> 1);
			byte *pbDst = prgbDst + xpOn;
			byte *pbDst2 = pbDst + cbRowDst;
			byte bT;
			long cactLoop;

			if (xpOn & 1)
				{
				// do leading single byte
				bT = *pbSrc++;
				*pbDst++ = bT;
				*pbDst2++ = bT;
				xpOn++;
				}

			for (cactLoop = (xpOff - xpOn) >> 1; cactLoop > 0; cactLoop--)
				{
				bT = *pbSrc++;
				pbDst[0] = bT;
				pbDst[1] = bT;
				pbDst += 2;
				pbDst2[0] = bT;
				pbDst2[1] = bT;
				pbDst2 += 2;
				}

			if (xpOff & 1)
				{
				// do the trailing byte
				bT = *pbSrc;
				*pbDst = bT;
				*pbDst2 = bT;
				}
#endif //!IN_80386

			if (klwMax == (xpOn = regsc.XpFetch()))
				break;
			xpOn += dxpBase;
			AssertIn(xpOn - 1, xpOff, rcClip.Dxp() - 1 + dxpBase);
			}
		dypAdvance = 2;
		goto LAdvance;

LOneRow:
		// copy just one row to the destination
		for (;;)
			{
			xpOff = regsc.XpFetch() + dxpBase;
			AssertIn(xpOff - 1, xpOn, rcClip.Dxp() + dxpBase);

#ifdef IN_80386
			// copy just one row to the destination in native
			#define pbDstReg edi
			#define pbSrcReg esi
			#define lwTReg edx

			__asm
				{
				// lwTReg = xpOn;
				// pbDstReg = prgbDst + xpOn;
				// pbSrcReg = prgbSrc + (xpOn >> 1);
				mov		lwTReg,xpOn
				mov		pbSrcReg,lwTReg
				mov		pbDstReg,lwTReg
				sar		pbSrcReg,1
				add		pbDstReg,prgbDst
				add		pbSrcReg,prgbSrc

				// if (!(lwTReg & 1)) goto LGetCount1;
				test	lwTReg,1
				mov		ecx,xpOff
				jz		LGetCount1

				// move the single leading byte
				// lwTReg++;
				// al = *pbSrcReg++;
				// *pbDstReg++ = al;
				inc		lwTReg
				mov		al,[pbSrcReg]
				inc		pbSrcReg
				inc		pbDstReg
				mov		[pbDstReg-1],al

LGetCount1:
				// ecx = xpOff - lwTReg;
				// ecx >>= 1;
				// if (ecx <= 0) goto LLastByte1;
				sub		ecx,lwTReg
				sar		ecx,1
				jle		LLastByte1

LLoop1:
				// al = *pbSrcReg++;
				// ah = al;
				// *(short *)pbDstReg = ax;
				// pbDstReg += 2;
				mov		al,[pbSrcReg]
				inc		pbSrcReg
				add		pbDstReg,2
				mov		ah,al
				mov		[pbDstReg-2],ax

				// if (--ecx != 0) goto LLoop1;
				loop	LLoop1

LLastByte1:
				// if (!(xpOff & 1)) goto LDone1;
				test	xpOff,1
				jz		LDone1

				// move the single trailing byte
				// al = *pbSrcReg;
				// *pbDstReg = al;
				mov		al,[pbSrcReg]
				mov		[pbDstReg],al
LDone1:
				}

			#undef pbDstReg
			#undef pbSrcReg
			#undef lwTReg
#else //!IN_80386
			// copy just one row to the destination in C code
			byte bT;
			byte *pbSrc = prgbSrc + (xpOn >> 1);
			byte *pbDst = prgbDst + xpOn;
			long cactLoop;

			if (xpOn & 1)
				{
				// do leading single byte
				*pbDst++ = *pbSrc++;
				xpOn++;
				}

			for (cactLoop = (xpOff - xpOn) >> 1; cactLoop > 0; cactLoop--)
				{
				bT = *pbSrc++;
				pbDst[0] = bT;
				pbDst[1] = bT;
				pbDst += 2;
				}

			if (xpOff & 1)
				{
				// do the trailing byte
				*pbDst = *pbSrc;
				}
#endif //!IN_80386

			if (klwMax == (xpOn = regsc.XpFetch()))
				break;
			xpOn += dxpBase;
			AssertIn(xpOn - 1, xpOff, rcClip.Dxp() - 1 + dxpBase);
			}
		dypAdvance = 1;

LAdvance:
		if ((yp += dypAdvance) >= rcClip.ypBottom)
			break;
		regsc.ScanNext(dypAdvance);
		prgbDst += dypAdvance * cbRowDst;
		prgbSrc += (dypAdvance >> 1) * cbRowSrc;
		if (dypAdvance & 1)
			{
			if (fSecondRow)
				prgbSrc += cbRowSrc;
			fSecondRow = !fSecondRow;
			}
		}
}


/***************************************************************************
	This does a 2x vertical and 1x horizontal stretch blt, clipped to prcClip
	and pregnClip. The clipping is expressed in destination coordinates.
***************************************************************************/
void DoubleVertStretch(byte *prgbSrc, long cbRowSrc, long dypSrc, RC *prcSrc,
	byte *prgbDst, long cbRowDst, long dypDst, long xpDst, long ypDst,
	RC *prcClip, PREGN pregnClip)
{
	AssertPvCb(prgbSrc, LwMul(cbRowSrc, dypSrc));
	AssertPvCb(prgbDst, LwMul(cbRowDst, dypDst));
	AssertVarMem(prcSrc);
	Assert(prcSrc->xpLeft >= 0 && prcSrc->ypTop >= 0 &&
		prcSrc->xpRight <= cbRowSrc && prcSrc->ypBottom <= dypSrc,
		"Source rectangle not in source bitmap!");
	AssertNilOrVarMem(prcClip);
	AssertNilOrPo(pregnClip, 0);

	long xpOn, xpOff, dypAdvance, dxpBase, yp;
	bool fSecondRow;
	REGSC regsc;
	RC rcT(xpDst, ypDst, xpDst + prcSrc->Dxp(), ypDst + 2 * prcSrc->Dyp());
	RC rcClip(0, 0, cbRowDst, dypDst);

	if (!rcClip.FIntersect(&rcT))
		return;
	if (pvNil != prcClip && !rcClip.FIntersect(prcClip))
		return;

	// Set up the region scanner
	if (pvNil != pregnClip)
		regsc.Init(pregnClip, &rcClip);
	else
		regsc.InitRc(&rcClip, &rcClip);
	dxpBase = rcClip.xpLeft - xpDst;

	// move to rcClip.ypTop
	yp = rcClip.ypTop;
	prgbSrc += prcSrc->xpLeft +
		LwMul(prcSrc->ypTop + ((yp - ypDst) >> 1), cbRowSrc);
	prgbDst += xpDst + LwMul(yp, cbRowDst);
	fSecondRow = (yp - ypDst) & 1;

	for (;;)
		{
		if (klwMax == (xpOn = regsc.XpCur()))
			{
			//empty strip of the region
			dypAdvance = regsc.DypCur();
			goto LAdvance;
			}
		xpOn += dxpBase;
		if (fSecondRow || (regsc.DypCur() == 1))
			goto LOneRow;

		// copy two rows to the destination
		for (;;)
			{
			xpOff = regsc.XpFetch() + dxpBase;
			AssertIn(xpOff - 1, xpOn, rcClip.Dxp() + dxpBase);

#ifdef IN_80386

			// copy two rows to the destination in native
			__asm
				{
				// edi = prgbDst + xpOn;
				// esi = eax = prgbSrc + xpOn;
				// ebx = pbDstReg + cbRowDst;
				// ecx = xpOff - xpOn
				mov		edx,xpOn
				mov		ecx,xpOff
				mov		edi,edx
				mov		esi,edx
				sub		ecx,edx
				add		edi,prgbDst
				add		esi,prgbSrc
				mov		ebx,edi
				mov		eax,esi
				add		ebx,cbRowDst

				// copy the first row
				mov		edx,ecx
				shr		ecx,2
				rep		movsd
				mov		ecx,edx
				and		ecx,3
				rep		movsb

				// prepare to copy the second row
				mov		esi,eax
				mov		edi,ebx
				mov		ecx,edx

				// copy the second row
				shr		ecx,2
				and		edx,3
				rep		movsd
				mov		ecx,edx
				rep		movsb
				}

#else //!IN_80386

			// copy two rows to the destination in C code
			CopyPb(prgbSrc + xpOn, prgbDst + xpOn, xpOff - xpOn);
			CopyPb(prgbSrc + xpOn + cbRowDst, prgbDst + xpOn, xpOff - xpOn);

#endif //!IN_80386

			if (klwMax == (xpOn = regsc.XpFetch()))
				break;
			xpOn += dxpBase;
			AssertIn(xpOn - 1, xpOff, rcClip.Dxp() - 1 + dxpBase);
			}
		dypAdvance = 2;
		goto LAdvance;

LOneRow:
		// copy just one row to the destination
		for (;;)
			{
			xpOff = regsc.XpFetch() + dxpBase;
			AssertIn(xpOff - 1, xpOn, rcClip.Dxp() + dxpBase);

#ifdef IN_80386

			// copy one row to the destination in native
			__asm
				{
				// edi = prgbDst + xpOn;
				// esi = prgbSrc + xpOn;
				// ecx = xpOff - xpOn
				mov		edx,xpOn
				mov		ecx,xpOff
				mov		edi,edx
				mov		esi,edx
				sub		ecx,edx
				add		edi,prgbDst
				add		esi,prgbSrc

				// copy the row
				mov		edx,ecx
				shr		ecx,2
				and		edx,3
				rep		movsd
				mov		ecx,edx
				rep		movsb
				}

#else //!IN_80386

			// copy one row to the destination in C code
			CopyPb(prgbSrc + xpOn, prgbDst + xpOn, xpOff - xpOn);

#endif //!IN_80386

			if (klwMax == (xpOn = regsc.XpFetch()))
				break;
			xpOn += dxpBase;
			AssertIn(xpOn - 1, xpOff, rcClip.Dxp() - 1 + dxpBase);
			}
		dypAdvance = 1;

LAdvance:
		if ((yp += dypAdvance) >= rcClip.ypBottom)
			break;
		regsc.ScanNext(dypAdvance);
		prgbDst += dypAdvance * cbRowDst;
		prgbSrc += (dypAdvance >> 1) * cbRowSrc;
		if (dypAdvance & 1)
			{
			if (fSecondRow)
				prgbSrc += cbRowSrc;
			fSecondRow = !fSecondRow;
			}
		}
}


